package com.ruoyi.framework.aspectj;

import com.github.pagehelper.PageInfo;
import com.ruoyi.common.annotation.CacheZZ;
import com.ruoyi.common.annotation.CacheZZRemove;
import com.ruoyi.common.annotation.CacheZZTable;
import com.ruoyi.common.constant.HttpStatus;
import com.ruoyi.common.core.page.PageDomain;
import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.common.core.page.TableSupport;
import com.ruoyi.common.core.redis.RedisUtil;
import com.ruoyi.common.exception.CustomException;
import com.ruoyi.common.utils.StringTools;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;

@Aspect
@Component
//@Profile(value = "!druid")
public class CacheAspect {

    private static final Logger log = LoggerFactory.getLogger(CacheAspect.class);

    @Autowired
    private RedisUtil redisUtil;

    /**
     * 前缀
     */
    @Value("${spring.profiles.active}")
    private String prefix;

    /**
     * 缓存注解开关
     */
    @Value("${cacheAspect.cacheSwitch}")
    private boolean cacheSwitch;

    /**
     * 表达式解析器
     */
    ExpressionParser parser = new SpelExpressionParser();

    LocalVariableTableParameterNameDiscoverer discoverer = new LocalVariableTableParameterNameDiscoverer();

    /**
     * 分隔符
     */
    private static final String separator = ":";

    /**
     * 缓存添加在Hash中
     */
    @Pointcut("@annotation(com.ruoyi.common.annotation.CacheZZTable)")
    public void CacheZZTable() {
    }

    /**
     * 单个添加缓存
     */
    @Pointcut("@annotation(com.ruoyi.common.annotation.CacheZZ)")
    public void CacheZZ() {
    }

    /**
     * 清除缓存
     */
    @Pointcut("@annotation(com.ruoyi.common.annotation.CacheZZRemove)")
    public void CacheZZRemove() {
    }

    /**
     * 使用环绕增强实现AOP
     * Hash缓存放入
     */
    @Around("CacheZZTable()")
    public Object doCacheZZHash(ProceedingJoinPoint point) {
        Object result = null;
        try {
            //缓存开关
            if (!cacheSwitch) {
                result = point.proceed();
                return result;
            }
            CacheZZTable cacheZZTable = getCacheHashHelp(point);

            if (cacheZZTable == null) {
                throw new CustomException("获取 @CacheZZHash 注解出现错误！");
            }

            //获取key
            String hashKey = cacheZZTable.hashKey();
            //缓存失效时间
            long timeOut = cacheZZTable.timeOut();
            //目标方法的方法名
            String functionName = point.getSignature().getName();
            //目标方法的参数列表
            String params = Arrays.toString(point.getArgs());

            //组合hash缓存key
            String key = getKey(hashKey);

            PageDomain pageDomain = TableSupport.buildPageRequest();
            Integer pageNum = pageDomain.getPageNum();
            Integer pageSize = pageDomain.getPageSize();

            //方法名+参数组合
            String item = functionName + separator + params + separator + pageNum + separator + pageSize;

            //根据键值查询缓存
            if (redisUtil.hHasKey(key, item)) {
                result = redisUtil.hget(key, item);
                log.info("CacheZZTable 缓存获取数据--》 key:" + key + " item:" + item);
            } else {
                //执行目标方法
                result = point.proceed();
                //存入缓存
                redisUtil.hset(key, item, result, timeOut);
                log.info("CacheZZTable 缓存添加--》 key:" + key + " item:" + item);
            }
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
        return result;
    }

    /**
     * 解决查询列表时，redis序列化后导致的类型转换错误的问题
     */
    public Object methodReturnType(ProceedingJoinPoint point, Object obj) throws IllegalAccessException, InstantiationException {
        Signature signature = point.getSignature();
        if (signature instanceof MethodSignature) {
            //获取返回值类型
            MethodSignature methodSignature = (MethodSignature) signature;
            Method method = methodSignature.getMethod();
            Class<?> methodReturnType = method.getReturnType();
            Object o = methodReturnType.newInstance();

            if (o instanceof TableDataInfo && obj instanceof TableDataInfo) {
                TableDataInfo tableDataInfo = (TableDataInfo) obj;
                return tableDataInfo.getRows();
            }
            if (o instanceof TableDataInfo && obj instanceof List) {
                List<?> list = (List<?>) obj;
                TableDataInfo rspData = new TableDataInfo();
                rspData.setCode(HttpStatus.SUCCESS);
                rspData.setMsg("查询成功");
                rspData.setRows(list);
                rspData.setTotal(new PageInfo(list).getTotal());
                return rspData;
            }
        }
        return obj;
    }

    /**
     * 普通缓存放入
     */
    @Around("CacheZZ()")
    public Object doCacheZZ(ProceedingJoinPoint point) {
        Object result = null;
        try {
            //缓存开关
            if (!cacheSwitch) {
                result = point.proceed();
                return result;
            }
            CacheZZ cacheZZ = getCacheHelp(point);

            if (cacheZZ == null) {
                throw new CustomException("获取 @cacheZZ 注解出现错误！");
            }

            String hashKey = cacheZZ.hashKey();
            //缓存失效时间
            long timeOut = cacheZZ.timeOut();
            //目标方法的方法名
            String functionName = point.getSignature().getName();
            //目标方法的参数
            String params = StringTools.objString(point.getArgs()[0]);

            //组合hash缓存key
            String key = getKey(hashKey);

            //方法名+参数组合
//            String item = functionName + separator + params;

            //根据键值查询缓存
            if (redisUtil.hHasKey(key, params)) {
                result = redisUtil.hget(key, params);
                log.info("CacheZZ 缓存获取数据--》 key:" + key + " item:" + params);
            } else {
                //执行目标方法
                result = point.proceed();
                //存入缓存
                redisUtil.hset(key, params, result, timeOut);
                log.info("CacheZZ 缓存添加--》 key:" + key);
            }
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
        return result;
    }

    /**
     * 清除缓存
     */
    @Before("CacheZZRemove()")
    public void doCacheZZRemove(JoinPoint point) {
        try {
            //缓存开关
            if (!cacheSwitch) {
                return;
            }
            CacheZZRemove cacheZZRemove = getClearCache(point);

            if (cacheZZRemove == null) {
                throw new CustomException("获取 @cacheZZRemove 注解出现错误！");
            }

            //1.清除CacheZZTable内的缓存
            String[] cacheZZTable = cacheZZRemove.CacheZZTable();
            for (int i = 0; i < cacheZZTable.length; i++) {
                cacheZZTable[i] = getKey(cacheZZTable[i]);
                log.info("清除Hash缓存--》 key:" + cacheZZTable[i]);
            }
            redisUtil.del(cacheZZTable);

            //2.清除CacheZZ内的缓存
            String[] cacheZZ = cacheZZRemove.CacheZZ();
            if (cacheZZ == null || cacheZZ.length <= 0) {
                return;
            }
            //目标方法的方法名
            String functionName = point.getSignature().getName();
            //目标方法的参数
            Object obj = point.getArgs()[0];

            if (obj.getClass().isArray()) {
                //批量删除
                Object[] arrayObj = (Object[]) obj;
                Object[] items = new Object[arrayObj.length];
                for (int i = 0; i < arrayObj.length; i++) {
                    items[i] = arrayObj[i];
                }
                for (int i = 0; i < cacheZZ.length; i++) {
                    cacheZZ[i] = getKey(cacheZZ[i]);
                    redisUtil.hdel(cacheZZ[i], items);
                    log.info("清除普通缓存--》 key:" + cacheZZ[i] + "  item:" + items[i]);
                }
            } else {
                //對象删除
                Object[] args = point.getArgs();
                Method method = ((MethodSignature) point.getSignature()).getMethod();
                String[] params = discoverer.getParameterNames(method);
                EvaluationContext context = new StandardEvaluationContext();
                for (int len = 0; len < params.length; len++) {
                    context.setVariable(params[len], args[len]);
                }
                String keySpel = cacheZZRemove.updateKey();
                Expression keyExpression = parser.parseExpression(keySpel);
                String item = keyExpression.getValue(context, String.class);

                for (int i = 0; i < cacheZZ.length; i++) {
                    cacheZZ[i] = getKey(cacheZZ[i]);
                    redisUtil.hdel(cacheZZ[i], item);
                    log.info("清除普通缓存--》 key:" + cacheZZ[i] + "  item:" + item);
                }
            }

        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
    }

    /**
     * 获取存放Hash缓存的key
     */
    private String getKey(String hashKey) {
        String key = StringTools.lowerCase(hashKey);
        return prefix + separator + key;
    }

    /**
     * 是否存在注解，如果存在就获取
     */
    private CacheZZTable getCacheHashHelp(JoinPoint joinPoint) {
        Signature signature = joinPoint.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        Method method = methodSignature.getMethod();
        if (method != null) {
            return method.getAnnotation(CacheZZTable.class);
        }
        return null;
    }

    private CacheZZ getCacheHelp(JoinPoint joinPoint) {
        Signature signature = joinPoint.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        Method method = methodSignature.getMethod();
        if (method != null) {
            return method.getAnnotation(CacheZZ.class);
        }
        return null;
    }

    private CacheZZRemove getClearCache(JoinPoint joinPoint) {
        Signature signature = joinPoint.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        Method method = methodSignature.getMethod();
        if (method != null) {
            return method.getAnnotation(CacheZZRemove.class);
        }
        return null;
    }
}
